// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2012-2019. All rights reserved.
 * Description: support nand flash driver
 * Author: caizhiyong <caizhiyong@hisilicon.com>
 * Create: 2018-10-26
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/math64.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/kallsyms.h>

#include "spi_ids.h"
#include "hisfc300.h"
#include "mtdcore.h"
#include "hifmc.h"
#ifdef HW_ONT_CUT_VERSION
#include <linux/kallsyminfo.h>
#endif

#ifndef __tagtable
#define __tagtable(tag, fn) \
static const struct tagtable __tagtable_##fn __used __section(".taglist.init") = { tag, fn }
#endif

/* User opens flash protection */
#define USER_PROTECT_ON            1
/* User turns off flash protection */
#define USER_PROTECT_OFF           0

/* flash protection cancellation user setting */
static int sfc_protect_status = USER_PROTECT_ON;

#define SFC_PROTECT_ON             1  /* flash protection enable */
#define SFC_PROTECT_OFF            0  /* flash protection disable */

/* flash protection flag */
static int sfc_protect_flag = SFC_PROTECT_ON;

/* flash writing flag */
static int sfc_erase_write_flag = SPI_IF_ERASE_WRITE_DIS;

/* flash controller type sfc */
#define FLASH_MEMORY_CTRL_TYPE_SFC  0
/* flash controller type fmc */
#define FLASH_MEMORY_CTRL_TYPE_FMC  1
/* spi lock work */
static struct delayed_work spi_lock_work;
static unsigned int g_flash_ctrl_type = FLASH_MEMORY_CTRL_TYPE_SFC;

#ifdef CONFIG_MTD_PARTITIONS
#define mtd_has_partitions()             (1)
#else
#define mtd_has_partitions()             (0)
#endif

#define HISFC200_SUPPORT_READ   (SPI_IF_READ_STD | \
	SPI_IF_READ_FAST | SPI_IF_READ_DUAL)

#define HISFC200_SUPPORT_MAX_DUMMY (1)
#define HISFC200_SUPPORT_WRITE     (SPI_IF_WRITE_STD)

/* flash register read, max 256 bytes by default */
static int hisfc200_reg_buf_size = 256;
static unsigned int hisfc200_reg_buf_mask = 0xFF;   /* flash register read mask */

/*
 * Used to distinguish between mxu and ont, ont use the default value of 0,
 * mxu use a special value 0xff, mxu set in the uboot startup parameters
 */
int hi_sfc_type;

int hi_sfc_type_get(void)
{
	return hi_sfc_type;
}

static unsigned char g_spi_ids[8] = {0};  // macro MTD_CHIP_ID_LEN = 8

struct hisfc_ew_count {
	unsigned int ui_erasetimes;
	unsigned int ui_writetimes;
};

/* record times for flash erasing and writing */
struct hisfc_ew_count g_spi_ewtimes = {0};
EXPORT_SYMBOL(g_spi_ewtimes);

static int hisfc200_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
static int hisfc200_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
static void hisfc200_lock_timer(void);
static const char * const fmt[] = {"%u", "%uK", "%uM", "%uG", "%uT", "%uT"};
#ifdef CONFIG_MTD_CMDLINE_PARTS
static const char * const part_probes[] = { "cmdlinepart", NULL, };
#endif

static char *int_to_size(unsigned long long size)
{
	int i;
	static char buffer[20];

	for (i = 0; (i < 5) && !(size & 0x3FF) && size; i++)
		size = (size >> 10);

	if (sprintf(buffer, fmt[i], size) < 0)
		pr_err("HISFC300: %s fail!\n", __func__);
	return buffer;
}

static int hisfc300_read_cond_resched(struct hisfc_host *host)
{
	unsigned int loop = 0;

	while ((hisfc_read(host, HISFC300_CMD_CONFIG) & HISFC300_CMD_START)) {
		if (loop++ > HISFC300_CHECK_STATUS_LOOP_MAX) {
			pr_warn("HISFC300: controller operation time out!\n");
			return -EIO;
		}
		cond_resched();
	}
	return 0;
}

static int hisfc200_wait_ready(struct hisfc_host *host, struct hisfc_spi *spi)
{
	unsigned long regval;
	unsigned long deadline;

	deadline = jiffies + HISFC300_MAX_READY_WAIT_JIFFIES;
	do {
		hisfc_write(host, HISFC300_INS, SPI_CMD_RDSR);
		hisfc_write(host, HISFC300_CMD_CONFIG,
			HISFC300_CMD_CS(spi->chipselect) | HISFC300_CMD_DATA_CNT(1) |
			HISFC300_CMD_DATA_EN | HISFC300_CMD_DATA_RW | HISFC300_CMD_START);

		if (hisfc300_read_cond_resched(host))
			return 1;

		regval = hisfc_read(host, HISFC300_DATABUF1);
		if (!(regval & SPI_CMD_SR_WIP))
			return 0;

		cond_resched();
	} while (!time_after_eq(jiffies, deadline));

	pr_warn("!!! Wait spi flash ready timeout.\n");

	return 1;
}

/* read data used bus mode */
static int hisfc200_bus_read(struct mtd_info *mtd, loff_t from, size_t len,
	size_t *retlen, u_char *buf)
{
	int num;
	int result = -EIO;
	unsigned char *ptr = buf;
	struct hisfc_host *host = MTD_TO_HOST(mtd);
	struct hisfc_spi *spi = host->spi;

	/*
	 * refer to mtd driver function, (from + len)< from condition interceps outrange
	 * (from + len) > mtd->size protect operation is within valid range
	 */
	if (((from + len) < from) || ((from + len) > mtd->size)) {
		DBG_MSG("read area out of range.\n");
		return -EINVAL;
	}

	*retlen = 0;
	if (!len) {
		DBG_MSG("read length is 0.\n");
		return 0;
	}

	mutex_lock(&host->lock);

	if (hisfc200_wait_ready(host, spi))
		goto fail;

	while (len > 0) {
		while (from >= spi->chipsize) {
			from -= spi->chipsize;
			spi++;
			if (!spi->name)
				DBG_BUG("read memory out of range.\n");

			if (hisfc200_wait_ready(host, spi))
				goto fail;
		}

		num = ((from + len) >= spi->chipsize) ? (spi->chipsize - from) : len;
		while (num >= HISFC200_MEM_READ_SLICE) {
			memcpy(ptr, (char *)spi->iobase + from, HISFC200_MEM_READ_SLICE);

			ptr += HISFC200_MEM_READ_SLICE;
			from += HISFC200_MEM_READ_SLICE;
			len -= HISFC200_MEM_READ_SLICE;
			num -= HISFC200_MEM_READ_SLICE;

			cond_resched();
		}

		if (num) {
			memcpy(ptr, (char *)spi->iobase + from, num);
			from += num;
			ptr += num;
			len -= num;
		}
	}
	result = 0;
fail:
	mutex_unlock(&host->lock);
	*retlen = (size_t)(ptr - buf);
	return result;
}

static void hisfc200_read_ids(struct hisfc_host *host, int chipselect,
	unsigned char *buffer, int numread)
{
	int regindex = 0;
	unsigned long *ptr = (unsigned long *)buffer;

	if (numread > hisfc200_reg_buf_size)
		numread = hisfc200_reg_buf_size;

	hisfc_write(host, HISFC300_INS, SPI_CMD_RDID);
	hisfc_write(host, HISFC300_CMD_CONFIG,
		HISFC300_CMD_CS((unsigned int)chipselect) | HISFC300_CMD_DATA_EN |
		HISFC300_CMD_DATA_RW | HISFC300_CMD_DATA_CNT((unsigned int)numread) |
		HISFC300_CMD_START);

	if (hisfc300_read_cond_resched(host))
		return;

	/* reg read, rounded up to 3 */
	numread = (((unsigned int)numread + 0x03) & (~0x03));
	while (numread) {
		*ptr = hisfc_read(host, HISFC300_DATABUF1 + regindex);
		ptr += 1;
		regindex += 4; /* reg move 4 bits backward */
		numread -= 4; /* get 4 bits each time */
	}
}

int hisfc200_read_chipid(struct mtd_info *mtd, unsigned char *buffer, int numread)
{
	struct hisfc_host *host = NULL;
	int chipselect;

	if ((mtd == NULL) || (buffer == NULL))
		return -1;

	host = MTD_TO_HOST(mtd);

	for (chipselect = (CONFIG_HISFC300_CHIP_NUM - 1); chipselect >= 0; chipselect--) {
		/* ptr buffer is always returned, no extra judgment on returned value */
		hisfc200_read_ids(host, chipselect, buffer, numread);

		/* if chipid is invalid, try next one until success */
		if (!(buffer[0] | buffer[1] | buffer[2]) ||
			((buffer[0] & buffer[1] & buffer[2]) == 0xFF))
			continue;
		else
			break;
	}
	/* no valid chipid */
	if (chipselect < 0)
		return -1;

	return 0;
}

/* enable spi write. */
static int hisfc200_write_enable(struct hisfc_host *host, struct hisfc_spi *spi)
{
	hisfc_write(host, HISFC300_INS, SPI_CMD_WREN);

	hisfc_write(host, HISFC300_CMD_CONFIG,
		(HISFC300_CMD_CS(spi->chipselect) | HISFC300_CMD_START));

	if (hisfc300_read_cond_resched(host))
		return 1;

	return 0;
}

/* erase one block */
static int hisfc200_reg_erase_one_block(struct hisfc_host *host, struct hisfc_spi *spi,
	unsigned long offset, int cmdindex)
{
	if (hisfc200_wait_ready(host, spi))
		return 1;

	if (cmdindex >= MAX_SPI_OP)
		return 1;

	if (hisfc200_write_enable(host, spi))
		return 1;

	hisfc_write(host, HISFC300_INS, spi->erase[cmdindex].cmd);

	hisfc_write(host, HISFC300_ADDR, (offset & HISFC300_ADDR_MASK));

	hisfc_write(host, HISFC300_CMD_CONFIG,
		HISFC300_CMD_CS(spi->chipselect) |
		HISFC300_CMD_DUMMY_CNT(spi->erase[cmdindex].dummy) |
		HISFC300_CMD_ADDR_EN | HISFC300_CMD_START);

	if (hisfc300_read_cond_resched(host))
		return 1;

	/* record times for spi flash erasing */
	g_spi_ewtimes.ui_erasetimes++;

	return 0;
}

static int hisfc200_reg_write_one_buffer(struct hisfc_host *host, struct hisfc_spi *spi,
	unsigned int offset, const unsigned char *buf, int count)
{
	int num;
	int regindex = 0;
	int num_write = count;
	unsigned int regval;

	if (hisfc200_wait_ready(host, spi))
		return 1;

	if (hisfc200_write_enable(host, spi))
		return 1;

	hisfc_write(host, HISFC300_INS, spi->write->cmd);

	hisfc_write(host, HISFC300_ADDR, (offset & HISFC300_ADDR_MASK));

	while (count > 0) {
		num = ((count > 4) ? 4 : count);
		memcpy(&regval, buf, num);
		hisfc_write(host, (HISFC300_DATABUF1 + regindex), regval);

		buf += num;
		regindex += 4;
		count -= num;
	}

	hisfc_write(host, HISFC300_CMD_CONFIG,
		(((HISFC300_CMD_CS(spi->chipselect) |
		HISFC300_CMD_DUMMY_CNT(spi->write->dummy) |
		HISFC300_CMD_DATA_CNT((unsigned int)num_write) |
		HISFC300_CMD_ADDR_EN |
		HISFC300_CMD_DATA_EN) &
		(~HISFC300_CMD_DATA_RW)) |
		HISFC300_CMD_START));

	if (hisfc300_read_cond_resched(host))
		return 1;

	/* record times for spi flash writing */
	g_spi_ewtimes.ui_writetimes++;

	return 0;
}

static int hisfc200_reg_write(struct mtd_info *mtd, loff_t to, size_t len,
	size_t *retlen, const u_char *buf)
{
	int num;
	int result = -EIO;

	unsigned char *ptr = (unsigned char *)buf;
	struct hisfc_host *host = MTD_TO_HOST(mtd);
	struct hisfc_spi *spi = host->spi;

	if (!host) {
		DBG_MSG("mtd host invalid.\n");
		return -EINVAL;
	}

	if (((to + len) < to) || ((to + len) > mtd->size)) {
		DBG_MSG("write data out of range.\n");
		return -EINVAL;
	}

	*retlen = 0;
	if (!len) {
		DBG_MSG("write length is 0.\n");
		return 0;
	}

	mutex_lock(&host->lock);

	sfc_erase_write_flag = SPI_IF_ERASE_WRITE_EN;
	(void)hisfc200_unlock(mtd, 0, 0);
	if (hisfc200_wait_ready(host, spi))
		goto fail;

	if ((unsigned int)to & hisfc200_reg_buf_mask) {
		num = hisfc200_reg_buf_size - ((unsigned int)to & hisfc200_reg_buf_mask);
		if (num > (int)len)
			num = (int)len;

		while (to >= spi->chipsize) {
			to -= spi->chipsize;
			spi++;
			if (!spi->name)
				DBG_BUG("write memory out of range.\n");

			if (hisfc200_wait_ready(host, spi))
				goto fail;
		}
		if (hisfc200_reg_write_one_buffer(host, spi, to, ptr, num))
			goto fail;
		to += num;
		ptr += num;
		len -= num;
	}

	while (len > 0) {
		num = ((len >= hisfc200_reg_buf_size) ? hisfc200_reg_buf_size : len);
		while (to >= spi->chipsize) {
			to -= spi->chipsize;
			spi++;
			if (!spi->name)
				DBG_BUG("write memory out of range.\n");

			if (hisfc200_wait_ready(host, spi))
				goto fail;
		}
		if (hisfc200_reg_write_one_buffer(host, spi, to, ptr, num))
			goto fail;
		to += num;
		ptr += num;
		len -= num;
	}
	result = 0;
fail:
	sfc_erase_write_flag = SPI_IF_ERASE_WRITE_DIS;
	mutex_unlock(&host->lock);
	*retlen = (size_t)(ptr - buf);
	hisfc200_lock_timer();
	return result;
}

static unsigned int hisfc200_read_sr(struct hisfc_host *host, struct hisfc_spi *spi)
{
	/* 1. ins reg = read status instruction */
	hisfc_write(host, HISFC300_INS, SPI_CMD_RDSR);

	/* 2. cmd reg */
	hisfc_write(host, HISFC300_CMD_CONFIG, HISFC300_CMD_CS(spi->chipselect) |
		HISFC300_CMD_DATA_CNT(1) | HISFC300_CMD_DATA_EN |
		HISFC300_CMD_DATA_RW | HISFC300_CMD_START);

	/* 3. Wait util finished. */
	if (hisfc300_read_cond_resched(host))
		return ~0;

	/* 4. Get reg value from databuf. */
	return hisfc_read(host, HISFC300_DATABUF1);
}

static int hisfc200_write_sr(struct hisfc_host *host, struct hisfc_spi *spi, unsigned int value)
{
	/* 1. Wait util write enable. */
	if (hisfc200_wait_ready(host, spi))
		return -EIO;

	/* 2. Send write enable commands. */
	hisfc200_write_enable(host, spi);

	/* 3. ins reg = write status instruction */
	hisfc_write(host, HISFC300_INS, SPI_CMD_WRSR);

	/* 4. databuf = value */
	hisfc_write(host, HISFC300_DATABUF1, value);

	/* 5. cmd reg */
	hisfc_write(host, HISFC300_CMD_CONFIG, (((HISFC300_CMD_CS(spi->chipselect) |
		HISFC300_CMD_DATA_CNT(1) | HISFC300_CMD_DATA_EN) &
		(~HISFC300_CMD_DATA_RW)) | HISFC300_CMD_START));

	/* 6. Wait util finished. */
	if (hisfc300_read_cond_resched(host))
		return -EIO;

	/*
	 * sr cannot take effect immediately after being written
	 * add a delay to ensure sufficient time
	 */
	if (hisfc200_wait_ready(host, spi))
		return -EIO;

	return 0;
}

static void hisfc200_hardware_wp_enable(struct hisfc_host *host, struct hisfc_spi *spi)
{
	unsigned int regval;

	regval = hisfc_read(host, HISFC300_GLOBAL_CONFIG);
	regval |= HISFC300_CONFIG_WP_EN;
	hisfc_write(host, HISFC300_GLOBAL_CONFIG, regval);

	mdelay(1);
}

static void hisfc200_hardware_wp_disable(struct hisfc_host *host, struct hisfc_spi *spi)
{
	unsigned int regval;

	regval = hisfc_read(host, HISFC300_GLOBAL_CONFIG);
	regval &= ~HISFC300_CONFIG_WP_EN;
	hisfc_write(host, HISFC300_GLOBAL_CONFIG, regval);

	mdelay(1);
}

static int hisfc200_reg_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	struct hisfc_host *host = MTD_TO_HOST(mtd);
	struct hisfc_spi *spi = host->spi;

	int cmdindex;
	unsigned long long offset = instr->addr;
	unsigned long long len = instr->len;

	/* unsigned long long type, not reverse ensured by call point */
	if ((instr->addr + instr->len) > mtd->size) {
		DBG_MSG("erase area out of range of mtd.\n");
		return -EINVAL;
	}

	if ((unsigned int)instr->addr & (mtd->erasesize-1)) {
		DBG_MSG("erase start address is not alignment.\n");
		return -EINVAL;
	}

	if ((unsigned int)instr->len & (mtd->erasesize-1)) {
		DBG_MSG("erase length is not alignment.\n");
		return -EINVAL;
	}

	mutex_lock(&host->lock);
	sfc_erase_write_flag = SPI_IF_ERASE_WRITE_EN;
	(void)hisfc200_unlock(mtd, 0, 0);
	while (len) {
		if (spi->chipsize <= offset) {
			offset -= spi->chipsize;
			spi++;
			if (!spi->name)
				DBG_BUG("erase memory out of range.\n");
		}

		for (cmdindex = 0; cmdindex < MAX_SPI_OP; cmdindex++) {
			if ((spi->chipsize - offset) >= spi->erase[cmdindex].size &&
				len >= spi->erase[cmdindex].size)
				break;
		}

		if (hisfc200_reg_erase_one_block(host, spi, (unsigned long)offset, cmdindex)) {
			hisfc200_lock_timer();
			mutex_unlock(&host->lock);
			return -EIO;
		}

		offset += spi->erase[cmdindex].size;
		len -= spi->erase[cmdindex].size;
	}

	sfc_erase_write_flag = SPI_IF_ERASE_WRITE_DIS;
	mutex_unlock(&host->lock);
	hisfc200_lock_timer();

	return 0;
}

void hisfc200_lock_timer_handler(struct work_struct *work)
{
	struct mtd_info *mtd = NULL;
	struct hisfc_host *host = NULL;

	mtd = get_mtd_device_nm("hi_sfc");
	if (!mtd) {
		pr_err("not find hi_sfc in %s\n", __func__);
		return;
	}

	host = MTD_TO_HOST(mtd);

	if (sfc_erase_write_flag == SPI_IF_ERASE_WRITE_EN) {
		/* if writing, restart work queue */
		hisfc200_lock_timer();
	} else {
		mutex_lock(&host->lock);
		/* set flash write protection */
		(void)hisfc200_lock(mtd, 0, 0);
		mutex_unlock(&host->lock);
	}
}

static void hisfc200_lock_timer(void)
{
	if (sfc_protect_status == USER_PROTECT_OFF)
		return;

	/* work queue is enabled only when flash is not in protect status */
	if (sfc_protect_flag == SFC_PROTECT_OFF) {
		if (delayed_work_pending(&spi_lock_work)) {
			cancel_delayed_work(&spi_lock_work);
			flush_scheduled_work();
		}

		/* wait for 1s */
		schedule_delayed_work(&spi_lock_work, msecs_to_jiffies(1000));
	}
}

static int hisfc200_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
	struct hisfc_host *host = MTD_TO_HOST(mtd);
	struct hisfc_spi *spi = host->spi;
	unsigned char BP_temp;
	unsigned int reg_value;

	pr_info("Lock Spi Flash(cs%d):BP~0x%x!\n", spi->chipselect, spi->BP_bitmask);

	/*
	 * if flash protection is disabled by user cfg, unless restatrt probe with enabling it,
	 * protection keeps cancelled
	 */
	if (sfc_protect_status == USER_PROTECT_OFF)
		return 0;

	sfc_protect_flag = SFC_PROTECT_ON;
	/* read status reg */
	reg_value = hisfc200_read_sr(host, spi);
	BP_temp = SPI_CMD_SR_SRWD | (spi->BP_bitmask << 2);

	/* if protection on */
	if (BP_temp != (reg_value & BP_temp)) {
		hisfc200_hardware_wp_disable(host, spi);

		/* change reg value to enable protect */
		reg_value |=  BP_temp;

		/* write back to status reg */
		if (hisfc200_write_sr(host, spi, reg_value))
			pr_err("%s:(%d),hisfc200_write_sr fail\n", __func__, __LINE__);
	}

	hisfc200_hardware_wp_enable(host, spi);

	return 0;
}

static int hisfc200_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
	struct hisfc_host *host = MTD_TO_HOST(mtd);
	struct hisfc_spi *spi = host->spi;
	unsigned int reg_value;
	unsigned char BP_temp;

	/*
	 * if flash protection is disabled by user cfg, unless restatrt probe with enabling it,
	 * protection keeps cancelled
	 */
	if (sfc_protect_status == USER_PROTECT_OFF)
		return 0;

	/* flash is in non-protection status */
	if (sfc_protect_flag == SFC_PROTECT_OFF)
		return 0;

	pr_info("Unlock Spi Flash(cs%d):BP~0x%x!\n", spi->chipselect, spi->BP_bitmask);

	sfc_protect_flag = SFC_PROTECT_OFF;
	hisfc200_hardware_wp_disable(host, spi);

	/* read status reg */
	reg_value = hisfc200_read_sr(host, spi);
	BP_temp = SPI_CMD_SR_SRWD | (spi->BP_bitmask << 2);

	/* if protection cancelled */
	if (0 != (reg_value & BP_temp)) {
		/* change reg value to disable protect */
		reg_value &= ~BP_temp;

		/* write back to status reg */
		if (hisfc200_write_sr(host, spi, reg_value))
			pr_err("%s:(%d),hisfc200_write_sr fail\n", __func__, __LINE__);
	}

	return 0;
}

unsigned int hisfc200_protect_set(unsigned int wp)
{
	struct mtd_info *mtd = NULL;
	struct hisfc_host *host = NULL;
	struct hisfc_spi *spi = NULL;

	mtd = get_mtd_device_nm("hi_sfc");
	if (!mtd) {
		pr_err("not find hi_sfc in %s\n", __func__);
		return -EINVAL;
	}

	host = MTD_TO_HOST(mtd);
	spi = host->spi;

	mutex_lock(&host->lock);
	if (wp == USER_PROTECT_ON) {
		sfc_protect_status = USER_PROTECT_ON;
		(void)hisfc200_lock(mtd, 0, 0);
	} else {
		(void)hisfc200_unlock(mtd, 0, 0);
		sfc_protect_status = USER_PROTECT_OFF;
	}
	mutex_unlock(&host->lock);

	return 0;
}

unsigned int hisfc200_protect_get(unsigned int *wp)
{
	struct mtd_info *mtd = NULL;
	struct hisfc_host *host = NULL;
	struct hisfc_spi *spi = NULL;
	unsigned int reg_value;
	unsigned int wp_sr_mask;

	mtd = get_mtd_device_nm("hi_sfc");
	if (!mtd) {
		pr_err("not find nm hi_sfc in %s\n", __func__);
		return -EINVAL;
	}

	host = MTD_TO_HOST(mtd);
	spi = host->spi;
	wp_sr_mask = (SPI_CMD_SR_SRWD | (spi->BP_bitmask << HISFC300_CONFIG_WP_EN));

	mutex_lock(&host->lock);

	/* check status reg */
	reg_value = hisfc200_read_sr(host, spi);
	if (wp_sr_mask != (reg_value & wp_sr_mask)) {
		*wp = SFC_PROTECT_OFF;

		mutex_unlock(&host->lock);
		return 0;
	}

	/* check wp io */
	reg_value = hisfc_read(host, HISFC300_GLOBAL_CONFIG);
	if (0 == (reg_value & HISFC300_CONFIG_WP_EN)) {
		*wp = SFC_PROTECT_OFF;

		mutex_unlock(&host->lock);
		return 0;
	}

	*wp = SFC_PROTECT_ON;
	mutex_unlock(&host->lock);

	return 0;
}

unsigned int flash_protect_set(unsigned int wp)
{
	unsigned int ret;

	if (g_flash_ctrl_type == FLASH_MEMORY_CTRL_TYPE_FMC)
		ret = hifmc_protect_set(wp);
	else
		ret = hisfc200_protect_set(wp);

	return ret;
}
EXPORT_SYMBOL(flash_protect_set);

unsigned int flash_protect_get(unsigned int *wp)
{
	unsigned int ret;

	if (g_flash_ctrl_type == FLASH_MEMORY_CTRL_TYPE_FMC)
		ret = hifmc_protect_get(wp);
	else
		ret = hisfc200_protect_get(wp);

	return ret;
}
EXPORT_SYMBOL(flash_protect_get);

static unsigned int hisfc200_map_chipsize(unsigned long long chipsize)
{
	unsigned int shift = 0;

	chipsize >>= (19 - 3); /* 19: 512K; 3: Bytes -> bit */

	while (chipsize) {
		chipsize >>= 1;
		shift++;
	}
	return shift;
}

static int hisfc200_spi_probe(struct hisfc_host *host)
{
	int chipselect;
	unsigned int total = 0;
	unsigned char ids[8];
	struct spi_info *spiinfo = NULL;
	struct hisfc_spi *spi = host->spi;
	unsigned int reg_value = 0;

	host->num_chip = 0;
	for (chipselect = (CONFIG_HISFC300_CHIP_NUM - 1); chipselect >= 0; chipselect--) {
		memset(ids, 0, sizeof(ids));
		hisfc200_read_ids(host, chipselect, ids, sizeof(ids));

		if (!(ids[0] | ids[1] | ids[2]) || ((ids[0] & ids[1] & ids[2]) == 0xFF))
			continue;

		pr_info("Spi(cs%d) ID: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X\n",
			chipselect, ids[0], ids[1], ids[2], ids[3], ids[4], ids[5]);

		spiinfo = spi_serach_ids(ids, sizeof(ids));
		if (spiinfo) {
			spi->name = spiinfo->name;
			spi->chipselect = chipselect;
			spi->chipsize = spiinfo->chipsize;
			spi->erasesize = spiinfo->erasesize;
			spi->addrcycle = spiinfo->addrcycle;
			spi->BP_bitmask = spiinfo->BP_bitmask;

			spi_search_rw(spiinfo, spi->read, HISFC200_SUPPORT_READ,
				HISFC200_SUPPORT_MAX_DUMMY, 1);

			spi_search_rw(spiinfo, spi->write, HISFC200_SUPPORT_WRITE,
				HISFC200_SUPPORT_MAX_DUMMY, 0);

			DBG_MSG("spi->read->iftype:0x%02X, cmd:0x%02X, clock:0x%02X\n",
				spi->read->iftype, spi->read->cmd, spi->read->clock);
			DBG_MSG("spi->write->iftype:0x%02X, cmd:0x%02X\n, clock:0x%02X",
				spi->write->iftype, spi->write->cmd, spi->write->clock);

			spi_get_erase(spiinfo, spi->erase, &spi->erasesize);

			/* set chip select.*/
			reg_value = reg_value &
				(unsigned int)(hisfc200_map_chipsize(spi->chipsize) <<
				(unsigned int)(chipselect * 8));
			spi->iobase = (char *)host->iobase + total;

			pr_info("Spi(cs%d): ", spi->chipselect);
			pr_info("Block:%sB ",  int_to_size(spi->erasesize));
			pr_info("Chip:%sB ",   int_to_size(spi->chipsize));
			pr_info("(Name:%s)\n", spi->name);

			host->num_chip++;
			total += spi->chipsize;
			spi++;
		} else {
			pr_warn("Spi(cs%d): Unknown\n", chipselect);
		}
	}

	return host->num_chip;
}


static int hisfc200_probe(struct hisfc_host *host)
{
	struct mtd_info *mtd = host->mtd;

	/*Set Controller to V300 Mode*/
	hisfc_write(host, HISFC300_VERSION_SEL, 0x1);

	hisfc_write(host, HISFC300_TIMING2,
		HISFC300_TIMING_TCSS(0) | HISFC300_TIMING_TCSH(0) | HISFC300_TIMING_TSHSL(4));

	if (!hisfc200_spi_probe(host))
		return -1;

	mtd->_erase = hisfc200_reg_erase;
	mtd->_write = hisfc200_reg_write;
	mtd->_read = hisfc200_bus_read;
	mtd->_lock = hisfc200_lock;
	mtd->_unlock = hisfc200_unlock;
#ifdef CONFIG_RTOS_OPTIMIZE_ONT
	mtd->read_chip_id = hisfc200_read_chipid;
#endif
	if (hi_kernel_get_chip_id() == HI_CHIP_ID_5115S_E) {
		(void)hisfc200_lock(mtd, 0, 0);
	} else {
		/*
		 * 5116s chip does not apply to BP solution to implement flash protection
		 * use power-off protection solution of chip
		 */
		(void)hisfc200_unlock(mtd, 0, 0);
		sfc_protect_status = USER_PROTECT_OFF;
	}
	/* init delayed work */
	INIT_DELAYED_WORK(&spi_lock_work, hisfc200_lock_timer_handler);
	return 0;
}

#define MAX_MTD_PARTITIONS         (32)

struct partition_entry {
	char name[16];
	unsigned long long start;
	unsigned long long length;
	unsigned int flags;
};

struct partition_info {
	int parts_num;
	struct partition_entry entry[MAX_MTD_PARTITIONS];
	struct mtd_partition parts[MAX_MTD_PARTITIONS];
};
static struct partition_info ptn_info;

static int __init parse_spi_partitions(const struct tag *tag)
{
	int i;

	if (tag->hdr.size <= HISFC300_PARTITION_HEAD_LEN) {
		pr_info("%s(%d): tag->hdr.size <= 2\n", __func__, __LINE__);
		return 0;
	}
	ptn_info.parts_num = (tag->hdr.size - HISFC300_PARTITION_HEAD_LEN) /
		(sizeof(struct partition_entry) / sizeof(int));
	if (ptn_info.parts_num > MAX_MTD_PARTITIONS) {
		pr_info("parts_num > %u\n", MAX_MTD_PARTITIONS);
		return 0;
	}
	memcpy(ptn_info.entry, &tag->u, ptn_info.parts_num * sizeof(struct partition_entry));

	for (i = 0; i < ptn_info.parts_num; i++) {
		ptn_info.parts[i].name = ptn_info.entry[i].name;
		ptn_info.parts[i].size = (ptn_info.entry[i].length);
		ptn_info.parts[i].offset = (ptn_info.entry[i].start);
		ptn_info.parts[i].mask_flags = 0;
	}

	return 0;
}
__tagtable(HISFC300_TAG_TABLE_MAGIC, parse_spi_partitions);

static void hisfc300_probe_spi_size(struct hisfc_host *host, struct mtd_info *mtd)
{
	int i = 1;
	struct hisfc_spi *spi = host->spi;

	int total = spi->chipsize;
	int erasesize = spi->erasesize;
	int num_chip = host->num_chip;

	for (++spi; i < num_chip; i++, spi++)
		total += spi->chipsize;

	mtd->size = total;
	mtd->erasesize = erasesize;
}
#ifdef CONFIG_PM
static int hisfc200_suspend(struct platform_device *pltdev, pm_message_t state)
{
	int i;
	struct hisfc_host *host = platform_get_drvdata(pltdev);
	int num_chip = host->num_chip;

	for (i = 0; i < num_chip; i++)
		if (hisfc200_wait_ready(host, &host->spi[i]))
			pr_err("time out! num_chip:%d\n", num_chip);

	return 0;
}

static int hisfc300_driver_suspend(struct platform_device *pltdev, pm_message_t state)
{
	struct hisfc_host *host = platform_get_drvdata(pltdev);

	if (host->suspend)
		return (host->suspend)(pltdev, state);
	return 0;
}

static int hisfc300_driver_resume(struct platform_device *pltdev)
{
	struct hisfc_host *host = platform_get_drvdata(pltdev);

	if (host->resume)
		return (host->resume)(pltdev);
	return 0;
}
#endif

/* Support for /proc/flashchipid */
static ssize_t spiflashchipid_read_interval(struct file *file, char __user *buffer,
	size_t buflen, loff_t *fpos)
{
	int count;
	char *kbuf = NULL;

	if (*fpos == HISFC300_READ_CHIPID_MAGIC)
		return 0;

	/* copy chip id to user as string, include MTD_CHIP_ID_LEN*2 chars, '\n' and '\0' */
	kbuf = kzalloc(8 * 2 + 2, GFP_KERNEL);  // macro MTD_CHIP_ID_LEN = 8
	if (!kbuf)
		return -ENOMEM;

	// macro MTD_CHIP_ID_LEN = 8
	count = snprintf(kbuf, 8 * 2 + 2, "%02x%02x%02x%02x%02x%02x%02x%02x\n",
		g_spi_ids[0], g_spi_ids[1], g_spi_ids[2], g_spi_ids[3], g_spi_ids[4],
		g_spi_ids[5], g_spi_ids[6], g_spi_ids[7]);

	if (copy_to_user(buffer, kbuf, strlen(kbuf) + 1)) {
		kfree(kbuf);
		return -EFAULT;
	}

	*fpos = HISFC300_READ_CHIPID_MAGIC;
	kfree(kbuf);
	return count;
}

static ssize_t spiflashchipid_write_interval(struct file *file, const char __user *buffer,
	size_t buflen, loff_t *fpos)
{
	return (ssize_t)buflen;
}

static const struct proc_ops proc_spiflashchipid = {
	.proc_read = spiflashchipid_read_interval,
	.proc_open = NULL,
	.proc_write = spiflashchipid_write_interval,
	.proc_release = NULL,
};

static struct proc_dir_entry *proc_file_entry;

static int /*__devinit*/ hisfc300_driver_probe(struct platform_device *pltdev)
{
	int result = -EIO;
	struct hisfc_host *host = NULL;
	struct mtd_info *mtd = NULL;

	host = kzalloc(sizeof(struct hisfc_host), GFP_KERNEL);
	if (!host)
		return -ENOMEM;

	platform_set_drvdata(pltdev, host);

	host->regbase = ioremap_nocache(pltdev->resource[0].start,
		pltdev->resource[0].end - pltdev->resource[0].start + 1);
	if (!host->regbase) {
		pr_err("spi reg ioremap failed.\n");
		goto fail;
	}

	host->iobase = ioremap_nocache(pltdev->resource[1].start,
		pltdev->resource[1].end - pltdev->resource[1].start + 1);
	if (!host->iobase) {
		pr_err("spi buffer ioremap failed.\n");
		goto fail;
	}

	host->cfgreg = ioremap_nocache(pltdev->resource[2].start,
		pltdev->resource[2].end - pltdev->resource[2].start + 1);
	if (!host->cfgreg) {
		pr_err("spi cfgreg ioremap failed.\n");
		goto fail;
	}

	mutex_init(&host->lock);

	mtd = host->mtd;
	mtd->name = (char *)pltdev->name;
	mtd->type = MTD_NORFLASH;
	mtd->writesize = 1;
	mtd->flags = MTD_CAP_NORFLASH;
	mtd->owner = THIS_MODULE;

	if (hisfc200_probe(host)) {
		result = -ENODEV;
		goto fail;
	}
#ifdef CONFIG_PM
	host->suspend = hisfc200_suspend;
	host->resume = NULL;
#endif

	hisfc300_probe_spi_size(host, mtd);
#ifdef CONFIG_RTOS_OPTIMIZE_ONT
	if (mtd->read_chip_id(mtd, g_spi_ids, 8))  // macro MTD_CHIP_ID_LEN = 8
		pr_err("Hisilicon flash: get spi id failed\n");
#endif
	proc_file_entry = proc_create("flashchipid", 0440, 0, &proc_spiflashchipid);
	if (!proc_file_entry) {
		pr_err("Hisilicon flash: create proc file failed\n");
		result = -ENOMEM;
		goto fail;
	}

	if (mtd_has_partitions()) {
		int nr_parts = 0;

		pr_notice("Hisilicon flash: registering whole flash at once as master MTD\n");
		result = local_add_mtd_device(host->mtd);
		if (result) {
			pr_err("Hisilicon flash: register whole flash failedn\n");
			result = -ENOMEM;
			goto fail;
		}
#ifdef CONFIG_MTD_CMDLINE_PARTS
		{
			INIT_LIST_HEAD(&host->mtd->partitions);
			nr_parts = local_parse_mtd_partitions(host->mtd, part_probes, 0);
		}
#endif

		if (nr_parts > 0) {
			return 0; /* mtd partition table binding success, return */

		result = -ENODEV;
		(void)mtd_device_unregister(mtd);
		goto fail;
		}
	}

	/* if mtd_has_partitions() is 0 */
	if (!local_add_mtd_device(host->mtd))
		return 0;

	result = -ENODEV;

fail:
	if (proc_file_entry)
		remove_proc_entry("flashchipid", NULL);
	if (host->regbase)
		iounmap(host->regbase);
	if (host->iobase)
		iounmap(host->iobase);
	if (host->cfgreg)
		iounmap(host->cfgreg);

	kfree(host);
	platform_set_drvdata(pltdev, NULL);

	return result;
}

static int hisfc300_driver_remove(struct platform_device *pltdev)
{
	int err = 0;
	struct hisfc_host *host = platform_get_drvdata(pltdev);

	if (!host)
		return -EINVAL;

	if (host->add_partition == 1) {
#ifdef CONFIG_MTD_PARTITIONS
		err = local_del_mtd_partitions(host->mtd);
#endif
	} else {
		err = local_del_mtd_device(host->mtd);
	}

	if (err)
		pr_err("Hisilicon flash: delete mtd device failed\n");

	if (proc_file_entry)
		remove_proc_entry("flashchipid", NULL);
	if (host->regbase)
		iounmap(host->regbase);
	if (host->iobase)
		iounmap(host->iobase);
	if (host->cfgreg)
		iounmap(host->cfgreg);

	kfree(host);
	platform_set_drvdata(pltdev, NULL);

	return 0;
}

static struct platform_driver hisfc300_driver_pltdrv = {
	.probe = hisfc300_driver_probe,
	.remove = hisfc300_driver_remove,
#ifdef CONFIG_PM
	.suspend = hisfc300_driver_suspend,
	.resume = hisfc300_driver_resume,
#endif
	.driver.name = "hi_sfc",
	.driver.owner = THIS_MODULE,
	.driver.bus = &platform_bus_type,
};

static struct resource hisfc300_device_resources[] = {
	[0] = {
		.start = CONFIG_HISFC300_REG_BASE_ADDRESS,
		.end = CONFIG_HISFC300_REG_BASE_ADDRESS + HISFC300_REG_BASE_LEN - 1,
		.flags = IORESOURCE_MEM,
	},

	[1] = {
		.start = CONFIG_HISFC300_BUFFER_BASE_ADDRESS,
		.end = CONFIG_HISFC300_BUFFER_BASE_ADDRESS + HISFC300_BUFFER_BASE_LEN - 1,
		.flags = IORESOURCE_MEM,
	},

	[2] = {
		.start = CONFIG_HISFC300_PERIPHERY_REGBASE,
		.end = CONFIG_HISFC300_PERIPHERY_REGBASE + HISFC300_PERIPHERY_REG_LEN - 1,
		.flags = IORESOURCE_MEM,
	},
};

static struct platform_device hisfc300_device_pltdev = {
	.name = "hi_sfc",
	.id = -1,
	.dev.release = NULL,
	.num_resources = ARRAY_SIZE(hisfc300_device_resources),
	.resource = hisfc300_device_resources,
};

static int __init hisfc300_module_init(void)
{
	int result = 0;

	/* 0 ont 0xff mxu */
	if (hi_sfc_type_get() != 0x0)
		return result;

	memcpy(g_flash_cmd_line, save_cmd_line, COMMAND_LINE_SIZE);

	/* Determine the controller type according to the startup parameters */
	if (strstr(g_flash_cmd_line, "flash_control=fmc") != NULL) {
		if (strstr(g_flash_cmd_line, "flash_chip=spinor") == NULL)
			return 0;

		g_flash_ctrl_type = FLASH_MEMORY_CTRL_TYPE_FMC;
		result = hifmc_spinor_module_init();
		return 0;
	} else if (strstr(g_flash_cmd_line, "flash_control=sfc") == NULL) {
		return 0;
	}

	g_flash_ctrl_type = FLASH_MEMORY_CTRL_TYPE_SFC;
	hi_chip_id = hi_kernel_get_chip_id();
	/* 5115 use 256,5116S use 64,5116H / L / T also need to confirm */
	if (hi_chip_id == HI_CHIP_ID_5115S_E)
		hisfc200_reg_buf_size = 256;
	else
		hisfc200_reg_buf_size = 64;

	pr_info("Spi Flash Controller V300 Device Driver, Version 1.10\n");

	hisfc200_reg_buf_mask = hisfc200_reg_buf_size - 1;

	result = platform_driver_register(&hisfc300_driver_pltdrv);
	if (result < 0)
		return 0;

	result = platform_device_register(&hisfc300_device_pltdev);
	if (result < 0) {
		platform_driver_unregister(&hisfc300_driver_pltdrv);
		return 0;
	}

	return result;
}

static void __exit hisfc300_module_exit(void)
{
	if ((strstr(g_flash_cmd_line, "flash_control=fmc") != NULL) &&
		(strstr(g_flash_cmd_line, "flash_chip=spinor") != NULL)) {
		hifmc_spinor_module_exit();
		return;
	}
	platform_device_unregister(&hisfc300_device_pltdev);
	platform_driver_unregister(&hisfc300_driver_pltdrv);
}

#ifdef HW_ONT_CUT_VERSION
static unsigned long kallsyms_lookup_name_from_global(const char *name)
{
	int i = 0;
	unsigned long paddr = 0;

	for (; i < ARRAY_SIZE(g_sym_addr_map); i++) {
		if (strcmp(g_sym_addr_map[i].sym_name, name) == 0) {
			paddr = g_sym_addr_map[i].sym_addr;
			break;
		}
	}

	if (paddr == 0)
		return 0;

#ifdef CONFIG_THUMB2_KERNEL
	if (paddr)
		paddr |= 1;
#endif

	return paddr;
}

static unsigned long kallsyms_lookup_name_for_tiny(const char *name)
{
	static unsigned long (*local_trustlist)(const char *name);
	static void *(*local_symbol)(const char *symbol);
	unsigned long paddr;

	paddr = kallsyms_lookup_name_from_global(name);
	if (paddr != 0)
		return paddr;
	if (local_trustlist == NULL) {
		local_trustlist =
			(unsigned long(*)(const char *))kallsyms_lookup_name_from_global(
				"find_trustlist_symbols");
	}

	paddr = local_trustlist(name);
	if (paddr != 0)
		return paddr;

	if (local_symbol == NULL)
		local_symbol =
			(void *(*)(const char *))kallsyms_lookup_name_from_global("__symbol_get");

	return (unsigned long)local_symbol(name);
}
#endif

int local_del_mtd_partitions(struct mtd_info *mtd)
{
	int (*del_mtd_partitions)(struct mtd_info *mtd) = NULL;
#ifdef HW_ONT_CUT_VERSION
	del_mtd_partitions = (void(*))kallsyms_lookup_name_for_tiny("del_mtd_partitions");
#else
#ifdef CONFIG_RTOS_FIND_TRUSTLIST_SYMBOLS
	del_mtd_partitions = (void(*))find_trustlist_symbols("del_mtd_partitions");
	if (del_mtd_partitions == NULL)
		del_mtd_partitions =  (void(*))symbol_get(del_mtd_partitions);
#endif
#endif

#ifdef CONFIG_THUMB2_KERNEL
	del_mtd_partitions = (void(*))((unsigned long)del_mtd_partitions | 1);
#endif
	return del_mtd_partitions(mtd);
}

int local_del_mtd_device(struct mtd_info *mtd)
{
	int (*del_mtd_device)(struct mtd_info *mtd) = NULL;
#ifdef HW_ONT_CUT_VERSION
	del_mtd_device = (void(*))kallsyms_lookup_name_for_tiny("del_mtd_device");
#else
#ifdef CONFIG_RTOS_FIND_TRUSTLIST_SYMBOLS
	del_mtd_device = (void(*))find_trustlist_symbols("del_mtd_device");
	if (del_mtd_device == NULL)
		del_mtd_device = (void(*))symbol_get(del_mtd_device);
#endif
#endif

#ifdef CONFIG_THUMB2_KERNEL
	del_mtd_device = (void(*))((unsigned long)del_mtd_device | 1);
#endif
	return del_mtd_device(mtd);
}

MODULE_IMPORT_NS(HW_RTOS_NS);
module_init(hisfc300_module_init);
module_exit(hisfc300_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cai ZhiYong");
MODULE_DESCRIPTION("Spi Flash Controller V300 Device Driver, Version 1.10");
